package com.hqd.ch03.v51.boot.autoconfigure.web.servlet;

/*
 * Copyright 2012-2021 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


import com.hqd.ch03.v51.boot.context.properties.ConfigurationProperties;
import com.hqd.ch03.v51.boot.context.properties.DeprecatedConfigurationProperty;
import com.hqd.ch03.v51.boot.context.properties.IncompatibleConfigurationException;
import com.hqd.ch03.v51.context.validation.DefaultMessageCodesResolver;
import com.hqd.ch03.v51.web.http.MediaType;

import java.time.Duration;
import java.util.LinkedHashMap;
import java.util.Map;

@ConfigurationProperties(prefix = "spring.mvc")
public class WebMvcProperties {

    private final Format format = new Format();
    private final Async async = new Async();
    private final Servlet servlet = new Servlet();
    private final View view = new View();
    private final Contentnegotiation contentnegotiation = new Contentnegotiation();
    private final Pathmatch pathmatch = new Pathmatch();
    /**
     * Formatting strategy for message codes. For instance, 'PREFIX_ERROR_CODE'.
     */
    private DefaultMessageCodesResolver.Format messageCodesResolverFormat;
    /**
     * Whether to dispatch TRACE requests to the FrameworkServlet doService method.
     */
    private boolean dispatchTraceRequest = false;
    /**
     * Whether to dispatch OPTIONS requests to the FrameworkServlet doService method.
     */
    private boolean dispatchOptionsRequest = true;
    private boolean ignoreDefaultModelOnRedirect = true;
    private boolean publishRequestHandledEvents = true;
    private boolean throwExceptionIfNoHandlerFound = false;
    private boolean logRequestDetails;
    private boolean logResolvedException = false;
    /**
     * Path pattern used for static resources.
     */
    private String staticPathPattern = "/**";

    public DefaultMessageCodesResolver.Format getMessageCodesResolverFormat() {
        return this.messageCodesResolverFormat;
    }

    public void setMessageCodesResolverFormat(DefaultMessageCodesResolver.Format messageCodesResolverFormat) {
        this.messageCodesResolverFormat = messageCodesResolverFormat;
    }

    @Deprecated
    @DeprecatedConfigurationProperty(replacement = "spring.mvc.format.date")
    public String getDateFormat() {
        return this.format.getDate();
    }

    @Deprecated
    public void setDateFormat(String dateFormat) {
        this.format.setDate(dateFormat);
    }

    public Format getFormat() {
        return this.format;
    }

    public boolean isIgnoreDefaultModelOnRedirect() {
        return this.ignoreDefaultModelOnRedirect;
    }

    public void setIgnoreDefaultModelOnRedirect(boolean ignoreDefaultModelOnRedirect) {
        this.ignoreDefaultModelOnRedirect = ignoreDefaultModelOnRedirect;
    }

    public boolean isPublishRequestHandledEvents() {
        return this.publishRequestHandledEvents;
    }

    public void setPublishRequestHandledEvents(boolean publishRequestHandledEvents) {
        this.publishRequestHandledEvents = publishRequestHandledEvents;
    }

    public boolean isThrowExceptionIfNoHandlerFound() {
        return this.throwExceptionIfNoHandlerFound;
    }

    public void setThrowExceptionIfNoHandlerFound(boolean throwExceptionIfNoHandlerFound) {
        this.throwExceptionIfNoHandlerFound = throwExceptionIfNoHandlerFound;
    }

    public boolean isLogRequestDetails() {
        return this.logRequestDetails;
    }

    public void setLogRequestDetails(boolean logRequestDetails) {
        this.logRequestDetails = logRequestDetails;
    }

    public boolean isLogResolvedException() {
        return this.logResolvedException;
    }

    public void setLogResolvedException(boolean logResolvedException) {
        this.logResolvedException = logResolvedException;
    }

    public boolean isDispatchOptionsRequest() {
        return this.dispatchOptionsRequest;
    }

    public void setDispatchOptionsRequest(boolean dispatchOptionsRequest) {
        this.dispatchOptionsRequest = dispatchOptionsRequest;
    }

    public boolean isDispatchTraceRequest() {
        return this.dispatchTraceRequest;
    }

    public void setDispatchTraceRequest(boolean dispatchTraceRequest) {
        this.dispatchTraceRequest = dispatchTraceRequest;
    }

    public String getStaticPathPattern() {
        return this.staticPathPattern;
    }

    public void setStaticPathPattern(String staticPathPattern) {
        this.staticPathPattern = staticPathPattern;
    }

    public Async getAsync() {
        return this.async;
    }

    public Servlet getServlet() {
        return this.servlet;
    }

    public View getView() {
        return this.view;
    }

    public Contentnegotiation getContentnegotiation() {
        return this.contentnegotiation;
    }

    public Pathmatch getPathmatch() {
        return this.pathmatch;
    }

    public void checkConfiguration() {
        if (this.getPathmatch().getMatchingStrategy() == MatchingStrategy.PATH_PATTERN_PARSER) {
            if (this.getPathmatch().isUseSuffixPattern()) {
                throw new IncompatibleConfigurationException("spring.mvc.pathmatch.matching-strategy",
                        "spring.mvc.pathmatch.use-suffix-pattern");
            }
            if (this.getPathmatch().isUseRegisteredSuffixPattern()) {
                throw new IncompatibleConfigurationException("spring.mvc.pathmatch.matching-strategy",
                        "spring.mvc.pathmatch.use-registered-suffix-pattern");
            }
        }
    }

    /**
     * Matching strategy options.
     *
     * @since 2.4.0
     */
    public enum MatchingStrategy {

        /**
         * Use the {@code AntPathMatcher} implementation.
         */
        ANT_PATH_MATCHER,

        /**
         * Use the {@code PathPatternParser} implementation.
         */
        PATH_PATTERN_PARSER

    }

    public static class Async {

        /**
         * Amount of time before asynchronous request handling times out. If this value is
         * not set, the default timeout of the underlying implementation is used.
         */
        private Duration requestTimeout;

        public Duration getRequestTimeout() {
            return this.requestTimeout;
        }

        public void setRequestTimeout(Duration requestTimeout) {
            this.requestTimeout = requestTimeout;
        }

    }

    public static class Servlet {

        /**
         * Path of the dispatcher servlet. Setting a custom value for this property is not
         * compatible with the PathPatternParser matching strategy.
         */
        private String path = "/";

        /**
         * Load on startup priority of the dispatcher servlet.
         */
        private int loadOnStartup = -1;

        public String getPath() {
            return this.path;
        }

        public void setPath(String path) {
            this.path = path;
        }

        public int getLoadOnStartup() {
            return this.loadOnStartup;
        }

        public void setLoadOnStartup(int loadOnStartup) {
            this.loadOnStartup = loadOnStartup;
        }

        public String getServletMapping() {
            if (this.path.equals("") || this.path.equals("/")) {
                return "/";
            }
            if (this.path.endsWith("/")) {
                return this.path + "*";
            }
            return this.path + "/*";
        }

        public String getPath(String path) {
            String prefix = getServletPrefix();
            if (!path.startsWith("/")) {
                path = "/" + path;
            }
            return prefix + path;
        }

        public String getServletPrefix() {
            String result = this.path;
            int index = result.indexOf('*');
            if (index != -1) {
                result = result.substring(0, index);
            }
            if (result.endsWith("/")) {
                result = result.substring(0, result.length() - 1);
            }
            return result;
        }

    }

    public static class View {

        /**
         * Spring MVC view prefix.
         */
        private String prefix;

        /**
         * Spring MVC view suffix.
         */
        private String suffix;

        public String getPrefix() {
            return this.prefix;
        }

        public void setPrefix(String prefix) {
            this.prefix = prefix;
        }

        public String getSuffix() {
            return this.suffix;
        }

        public void setSuffix(String suffix) {
            this.suffix = suffix;
        }

    }

    public static class Contentnegotiation {

        /**
         * Whether the path extension in the URL path should be used to determine the
         * requested media type. If enabled a request "/users.pdf" will be interpreted as
         * a request for "application/pdf" regardless of the 'Accept' header.
         */
        private boolean favorPathExtension = false;

        /**
         * Whether a request parameter ("format" by default) should be used to determine
         * the requested media type.
         */
        private boolean favorParameter = false;

        /**
         * Map file extensions to media types for content negotiation. For instance, yml
         * to text/yaml.
         */
        private Map<String, MediaType> mediaTypes = new LinkedHashMap<>();

        /**
         * Query parameter name to use when "favor-parameter" is enabled.
         */
        private String parameterName;

        @DeprecatedConfigurationProperty(
                reason = "Use of path extensions for request mapping and for content negotiation is discouraged.")
        @Deprecated
        public boolean isFavorPathExtension() {
            return this.favorPathExtension;
        }

        @Deprecated
        public void setFavorPathExtension(boolean favorPathExtension) {
            this.favorPathExtension = favorPathExtension;
        }

        public boolean isFavorParameter() {
            return this.favorParameter;
        }

        public void setFavorParameter(boolean favorParameter) {
            this.favorParameter = favorParameter;
        }

        public Map<String, MediaType> getMediaTypes() {
            return this.mediaTypes;
        }

        public void setMediaTypes(Map<String, MediaType> mediaTypes) {
            this.mediaTypes = mediaTypes;
        }

        public String getParameterName() {
            return this.parameterName;
        }

        public void setParameterName(String parameterName) {
            this.parameterName = parameterName;
        }

    }

    public static class Pathmatch {

        /**
         * Choice of strategy for matching request paths against registered mappings.
         */
        private MatchingStrategy matchingStrategy = MatchingStrategy.PATH_PATTERN_PARSER;

        /**
         * Whether to use suffix pattern match (".*") when matching patterns to requests.
         * If enabled a method mapped to "/users" also matches to "/users.*". Enabling
         * this option is not compatible with the PathPatternParser matching strategy.
         */
        private boolean useSuffixPattern = false;

        /**
         * Whether suffix pattern matching should work only against extensions registered
         * with "spring.mvc.contentnegotiation.media-types.*". This is generally
         * recommended to reduce ambiguity and to avoid issues such as when a "." appears
         * in the path for other reasons. Enabling this option is not compatible with the
         * PathPatternParser matching strategy.
         */
        private boolean useRegisteredSuffixPattern = false;

        public MatchingStrategy getMatchingStrategy() {
            return this.matchingStrategy;
        }

        public void setMatchingStrategy(MatchingStrategy matchingStrategy) {
            this.matchingStrategy = matchingStrategy;
        }

        @DeprecatedConfigurationProperty(
                reason = "Use of path extensions for request mapping and for content negotiation is discouraged.")
        @Deprecated
        public boolean isUseSuffixPattern() {
            return this.useSuffixPattern;
        }

        @Deprecated
        public void setUseSuffixPattern(boolean useSuffixPattern) {
            this.useSuffixPattern = useSuffixPattern;
        }

        @DeprecatedConfigurationProperty(
                reason = "Use of path extensions for request mapping and for content negotiation is discouraged.")
        @Deprecated
        public boolean isUseRegisteredSuffixPattern() {
            return this.useRegisteredSuffixPattern;
        }

        @Deprecated
        public void setUseRegisteredSuffixPattern(boolean useRegisteredSuffixPattern) {
            this.useRegisteredSuffixPattern = useRegisteredSuffixPattern;
        }

    }

    public static class Format {

        /**
         * Date format to use, for example 'dd/MM/yyyy'.
         */
        private String date;

        /**
         * Time format to use, for example 'HH:mm:ss'.
         */
        private String time;

        /**
         * Date-time format to use, for example 'yyyy-MM-dd HH:mm:ss'.
         */
        private String dateTime;

        public String getDate() {
            return this.date;
        }

        public void setDate(String date) {
            this.date = date;
        }

        public String getTime() {
            return this.time;
        }

        public void setTime(String time) {
            this.time = time;
        }

        public String getDateTime() {
            return this.dateTime;
        }

        public void setDateTime(String dateTime) {
            this.dateTime = dateTime;
        }

    }

}
